#include "qe_log.h"
#include "qe_string.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_argparse.h"



QELOG_DOMAIN(QELOG_DOMAIN_ARGPARSE)



static qe_const_str prefix_skip(qe_const_str str, qe_const_str prefix)
{
    qe_uint len = qe_strlen(prefix);
    return qe_strncmp(str, prefix, len) ? QE_NULL : str + len;
}

static qe_int prefix_cmp(qe_const_str str, qe_const_str prefix)
{
    for (;; str++, prefix++)
        if (!*prefix) {
            return 0;
        } else if (*str != *prefix) {
            return (unsigned char)*prefix - (unsigned char)*str;
        }
}

static qe_ret argp_get_val(qe_argparse *argp, qe_option *opt)
{
    char *endptr;

    if (!opt->value)
        goto skipped;

    switch (opt->type) {

    case QE_OPTION_BOOLEAN:
        if (opt->flags & QE_OPTION_UNSET) {
            *(qe_bool *)opt->value = qe_false;
        } else {
            *(qe_bool *)opt->value = qe_true;
        }
        break;

    case QE_OPTION_BIT:
        if (opt->flags & QE_OPTION_UNSET) {
            *(int *)opt->value &= ~opt->data;
        } else {
            *(int *)opt->value |= opt->data;
        }
        break;

    case QE_OPTION_STRING:
        if (argp->opt) {
            *(const char **)opt->value = argp->opt;
            argp->opt = QE_NULL;
        } else if (argp->argc > 1) {
            argp->argc--;
            *(const char **)opt->value = *++argp->argv;
        } else {
            qe_error("requires a value");
        }
        break;

    case QE_OPTION_INTEGER:
        if (argp->opt) {
            *(int *)opt->value = qe_strtoi(argp->opt);
            argp->opt = QE_NULL;
        } else if (argp->argc > 1) {
            argp->argc--;
            *(int *)opt->value = qe_strtoi(*++argp->argv);
        } else {
            qe_error("requires a value");
        }
        break;

    case QE_OPTION_FLOAT:
        if (argp->opt) {
            *(double *)opt->value = qe_strtof(argp->opt, &endptr);
            argp->opt = QE_NULL;
        } else if (argp->argc > 1) {
            argp->argc--;
            *(double *)opt->value = qe_strtof(*++argp->argv, &endptr);
        } else {
            qe_error("requires a value");
        }
        break;
    case QE_OPTION_SUBCMD:
        qe_debug("SUBCMD");
        break;
    default:
        qe_error("unknown option type %d", opt->type);
        return qe_err_notfind;
    }

skipped:
    if (opt->callback) {
        return opt->callback(argp, opt);
    }

    return qe_ok;
}

static void argp_options_check(qe_option *options)
{
    for (; options->type != QE_OPTION_END; options++) {
        switch (options->type) {
            case QE_OPTION_END:
            case QE_OPTION_BOOLEAN:
            case QE_OPTION_HELP:
            case QE_OPTION_BIT:
            case QE_OPTION_INTEGER:
            case QE_OPTION_FLOAT:
            case QE_OPTION_STRING:
            case QE_OPTION_GROUP:
            case QE_OPTION_ACTION:
            case QE_OPTION_SUBCMD:
                continue;
            default:
                qe_error("wrong option type: %d", options->type);
                break;
        }
    }
}

static qe_ret argp_long_opt(qe_argparse *argp, qe_option *options)
{
    for (; options->type != QE_OPTION_END; options++) {
        const char *rest;
        int opt_flags = 0;
        if (!options->long_name)
            continue;

        rest = prefix_skip(argp->argv[0] + 2, options->long_name);
        if (!rest) {

            if (prefix_cmp(argp->argv[0] + 2, "no-")) {
                continue;
            }
            rest = prefix_skip(argp->argv[0] + 2 + 3, options->long_name);
            if (!rest)
                continue;
            opt_flags |= QE_OPTION_UNSET;
        }
        if (*rest) {
            if (*rest != '=')
                continue;
            argp->opt = rest + 1;
        }
        return argp_get_val(argp, options);
    }
    return qe_err_notfind;
}

static qe_ret argp_short_opt(qe_argparse *argp, qe_option *options)
{
    for (; options->type != QE_OPTION_END; options++) {
        if (options->short_name == *argp->opt) {
            argp->opt = argp->opt[1] ? argp->opt + 1 : QE_NULL;
            return argp_get_val(argp, options);
        }
    }
    return qe_err_notfind;
}

static qe_ret argp_subcmd_opt(qe_argparse *argp, qe_option *options)
{
    qe_ret ret;
    const char *rest;

    for (; options->type != QE_OPTION_END; options++) {
        if (!options->long_name)
            continue;
        rest = prefix_skip(argp->argv[0], options->long_name);
        if (!rest) {
            continue;
        }
        return argp_get_val(argp, options);
    }
}

qe_ret qe_argp_init(qe_argparse *argp, qe_option *options, 
    qe_const_str *usages, qe_const_str description, 
    qe_const_str epilog)
{
    if (!argp || !options)
        return qe_err_param;

    qe_memset(argp, 0, sizeof(*argp));
    argp->options     = options;
    argp->usages      = usages;
    argp->description = description;
    argp->epilog      = epilog;
    return qe_ok;
}

qe_ret qe_argp_parse(qe_argparse *argp, qe_int argc, qe_const_str *argv)
{
    qe_ret ret = qe_ok;

    argp->argc = argc - 1;
    argp->argv = argv + 1;
    argp->outv = argv;

    argp_options_check(argp->options);

    for (; argp->argc; argp->argc--, argp->argv++) {

        const char *arg = argp->argv[0];

        // if (arg[0] != '-' || !arg[1]) {
        //     argp->outv[argp->cpidx++] = argp->argv[0];
        //     continue;
        // }

        if (arg[0] != '-') {
            if (!arg[1]) {
                argp->outv[argp->cpidx++] = argp->argv[0];
                continue;
            } else {
                argp->opt = arg;
                switch (argp_subcmd_opt(argp, argp->options)) {
                case qe_yield:
                    ret = qe_yield;
                    goto end;
                case qe_err_notfind:
                    goto unknown;
                }
            }
        }

        if (arg[1] != '-') {
            
            argp->opt = arg + 1;

            /* Short option */
            switch (argp_short_opt(argp, argp->options)) {
            case qe_yield:
                ret = qe_yield;
                goto end;
            case qe_err_notfind:
                goto unknown;
            }
            
            while (argp->opt) {
                qe_debug("opt %s", argp->opt);
                switch (argp_short_opt(argp, argp->options)) {
                case qe_yield:
                    ret = qe_yield;
                    goto end;
                case qe_err_notfind:
                    goto unknown;
                }
            }
            continue;
        }

        if (!arg[2]) {
            argp->argc--;
            argp->argv++;
            break;
        }

        /* Long option */
        switch (argp_long_opt(argp, argp->options)) {
        case qe_yield:
            ret = qe_yield;
            goto end;
        case qe_err_notfind:
            goto unknown;
        }
        continue;

unknown:
        qe_error("unknown option %s", argp->argv[0]);
        qe_argp_usage(argp);
        return qe_err_common;
    }

end:
    qe_memmove(argp->outv + argp->cpidx, argp->argv,
        argp->argc * sizeof(*argp->outv));
    argp->outv[argp->cpidx + argp->argc] = QE_NULL;

    return ret;
}

void qe_argp_usage(qe_argparse *argp)
{
    if (argp->usages) {
        const char *const *usages = argp->usages;
        qe_printf("Usage: %s"QE_ENDLINE, *usages++);
        while (*usages && **usages)
            qe_printf("   or: %s"QE_ENDLINE, *usages++);
    } else {
        qe_printf("Usage:"QE_ENDLINE);
    }

    // print description
    if (argp->description)
        qe_printf("%s"QE_ENDLINE, argp->description);

    qe_printf(QE_ENDLINE);

    const qe_option *options;

    qe_size usage_opts_width = 0;
    qe_size len;
    options = argp->options;
    for (; options->type != QE_OPTION_END; options++) {
        len = 0;
        if ((options)->short_name) {
            len += 2;
        }
        if ((options)->short_name && (options)->long_name) {
            len += 2;           // separator ", "
        }
        if ((options)->long_name) {
            len += qe_strlen((options)->long_name) + 2;
        }
        if (options->type == QE_OPTION_INTEGER) {
            len += qe_strlen("=<int>");
        }
        if (options->type == QE_OPTION_FLOAT) {
            len += qe_strlen("=<flt>");
        } else if (options->type == QE_OPTION_STRING) {
            len += qe_strlen("=<str>");
        }
        len = (len + 3) - ((len + 3) & 3);
        if (usage_opts_width < len) {
            usage_opts_width = len;
        }
    }
    usage_opts_width += 4;      // 4 spaces prefix

    options = argp->options;
    for (; options->type != QE_OPTION_END; options++) {
        qe_size pos = 0;
        qe_size pad = 0;
        if (options->type == QE_OPTION_GROUP) {
            qe_printf(QE_ENDLINE);
            qe_printf("%s", options->help);
            qe_printf(QE_ENDLINE);
            continue;
        }
        pos = qe_printf("    ");
        if (options->short_name) {
            pos += qe_printf("-%c", options->short_name);
        }
        if (options->long_name && options->short_name) {
            pos += qe_printf(", ");
        }
        if (options->long_name) {
            if (options->type != QE_OPTION_SUBCMD) {
                pos += qe_printf("--%s", options->long_name);
            } else {
                pos += qe_printf("%s", options->long_name);
            }
        }
        if (options->type == QE_OPTION_INTEGER) {
            pos += qe_printf("=<int>");
        } else if (options->type == QE_OPTION_FLOAT) {
            pos += qe_printf("=<flt>");
        } else if (options->type == QE_OPTION_STRING) {
            pos += qe_printf("=<str>");
        } else if (options->type == QE_OPTION_BIT) {
            pos += qe_printf("=<bit>");
        } else if (options->type == QE_OPTION_BOOLEAN) {
            pos += qe_printf("=<bool>");
        }

        if (pos <= usage_opts_width) {
            pad = usage_opts_width - pos;
        } else {
            qe_printf(QE_ENDLINE);
            pad = usage_opts_width;
        }
        qe_printf("%*s%s", (int)pad + 2, "", options->help);

        if (options->type == QE_OPTION_INTEGER) {
            qe_printf(" default %d", *(qe_int *)options->value);
        } else if (options->type == QE_OPTION_FLOAT) {
            qe_printf(" default %f", *(qe_double *)options->value);
        } 
        else if (options->type == QE_OPTION_STRING) {
            qe_printf(" default %s", *(qe_str *)options->value);
        } 
        else if (options->type == QE_OPTION_BIT) {
            qe_printf(" default %d", *(qe_int *)options->value);
        } 
        else if (options->type == QE_OPTION_BOOLEAN) {
            qe_printf(" default %d", *(qe_bool *)options->value);
        }

        qe_printf(QE_ENDLINE);
    }

    // print epilog
    if (argp->epilog)
        qe_printf("%s"QE_ENDLINE, argp->epilog);
}

qe_ret qe_argp_help_cb_no_exit(qe_argparse *parse, qe_option *option)
{
    (void)option;
    qe_argp_usage(parse);
    return qe_yield;
}